#include "IniCPP.h"

using namespace INI;

IniCPP::IniCPP( std::string iniFile )
{

	this->iniFile = iniFile;
	
	this->iniStream_in.open( this->iniFile.c_str(), std::ios::in );
	
	if( !( this->iniStream_in ) )
	{
		
		InvalidIniCPPException exc( "Invalid .ini file provided" );
		throw exc;
		
	}
	
	SECTION section;
	this->sections[ "_default_" ] = section;

}

void IniCPP::readIni()
{

	unsigned int lineNumber = 1;
	std::string line;
	std::getline( this->iniStream_in, line );
	
	SECTION * currentSection = &( (*this)[ "_default_" ] );
	
	while( this->iniStream_in )
	{
		
		if( line[ line.size() - 1 ] == '\r' )
			line.erase( line.size() - 1 );
	
		currentSection = &( this->parseLine( *currentSection, line, lineNumber ) );
		std::getline( this->iniStream_in, line );
		lineNumber++;
	
	}

}

void IniCPP::writeIni( std::string outIniFile )
{

	std::ofstream out( outIniFile.c_str(), std::ofstream::out );
	
	if( !out )
	{
	
		throw std::logic_error( "Invalid Out .ini File" );
	
	}
	
	out << ( std::string )( *this );
	out.close();

}

IniCPP::operator std::string() const
{

	std::stringstream stream;
	
	for( std::map< std::string, SECTION >::const_iterator it = this->sections.begin(); it != this->sections.end(); ++it )
	{
		
		SECTION section = it->second;
		if( it->first.compare( "_default_" ) != 0 )
			stream << "[" << it->first << "]\n";
		
		for( SECTION::const_iterator itt = section.begin(); itt != section.end(); ++itt )
		{
		
			stream << itt->first << " = " << ( std::string )itt->second << "\n";
		
		}
	
	}
	
	return stream.str();

}

SECTION & IniCPP::parseLine( SECTION & section, std::string line, unsigned int lineNumber )
{

	// Don't parse comments
	if( line[0] == ';' )
		return section;

	// New section
	if( line[0] == '[' && line[ line.size() - 1 ] == ']' )
	{
	
		std::string sectionName = line.substr( 1, line.size() - 2 );
		
		SECTION keys;
		this->sections[ sectionName ] = keys;
		
		return this->sections[ sectionName ];
	
	}
	else if( line != "" )
	{
	
		// key = value
		size_t equalsPos = line.find( '=' );
		
		// Not found, invalid entry
		if( equalsPos == std::string::npos )
		{
		
			std::stringstream stream;
			stream << lineNumber;
			InvalidINIFormatException exc( stream.str().c_str() );
			throw exc;
			
		}
		
		// Parse the key
		std::string key = line.substr( 0, equalsPos );
		
		// Clear ending spaces from key
		size_t lastSpace = std::string::npos;
		for( int i = ( key.size() - 1 ); i >= 0; --i )
		{
		
			if( !( isspace( line[i] ) ) )
				break;
			else
				lastSpace = i;
		
		}
		
		if( lastSpace != std::string::npos )
			key.erase( lastSpace, std::string::npos );
		
		// Parse the value
		std::string value = "";
		unsigned int numDecimals = 0;
		unsigned int numDigits = 0;
		unsigned int numAlphaPuncSpace = 0;
		for( unsigned int i = ( equalsPos + 1 ); i < line.size(); ++i )
		{
		
			if( isspace( line[i] ) )
			{
			
				// Only add space once the first character is detected
				if( numDecimals != 0 || numDigits != 0 || numAlphaPuncSpace != 0 )
				{
				
					value += line[i];
					numAlphaPuncSpace++;
				
				}
			
			}
			else if( isdigit( line[i] ) )
			{
			
				value += line[i];
				numDigits++;
			
			}
			else if( line[i] == '.' )
			{
			
				value += line[i];
				numDecimals++;
			
			}
			else if( isalpha( line[i] ) || ispunct( line[i] ) )
			{
			
				value += line[i];
				numAlphaPuncSpace++;
			
			}
		
		}
		
		// Get rid of opening / closing quotes, if present
		if( ( value[0] == '"' ) && ( value[ value.size() - 1 ] == '"' ) )
			value = value.substr( 1, value.size() - 2 );
		
		
		// Determine the type of value
		ValueType valType = TYPE_NULL;
		if( numAlphaPuncSpace > 0 )
			valType = TYPE_STRING;
		else if( numAlphaPuncSpace == 0 && numDecimals == 0 && numDigits > 0 )
			valType = TYPE_INT;
		else if( numAlphaPuncSpace == 0 && numDecimals == 1 && numDigits > 0 )
			valType = TYPE_FLOAT;
		else if( numAlphaPuncSpace == 0 && numDecimals > 1 )
			valType = TYPE_STRING;
		
		
		// Create the IniValue & Assign it
		section[ key ] = IniValue( valType );
		
		// Copy values into IniValue's void buffer
		switch( valType )
		{
		
			case TYPE_STRING :
			{
			
				section[ key ].value = new std::string( value );
				break;
			
			}
			
			case TYPE_INT :
			{
			
				int intVal;
				std::stringstream stream;
				stream << value;
				stream >> intVal;
				
				section[ key ].value = new int( intVal );
				
				break;
			
			}
			
			case TYPE_FLOAT :
			{
			
				double doubleVal;
				std::stringstream stream;
				stream << value;
				stream >> doubleVal;
				
				section[ key ].value = new double( doubleVal );
				
				break;
			
			}
			
			default :
			{
			
				section[ key ].value = NULL;
				
				break;
			
			}
		
		}
		
		return section;
	
	}
	
	return section;

}

SECTION & IniCPP::operator[]( const std::string & index )
{

	return this->sections[ index ];

}
